open Ast
open Cast

let rec varname_from_variable v = match v with 
        VarId s -> s
|       RecVar (v,s) -> (varname_from_variable v) ^ "." ^ s
|       ArrayIndDyn (s,v) -> s ^ "[" ^ (varname_from_variable v) ^ "]"
|       ArrayIndStat (s,i) -> s ^ "[" ^ (string_of_int i) ^ "]" 


let addtoend l e = List.rev (e :: (List.rev l))


let cop_from_op (o : op ) = match o with 
	  Add -> CAdd
	| Sub -> CSub
	| Mult -> CMult
	| Div -> CDiv
	| Equal -> CEqual
	| Neq -> CNeq
	| Less -> CLess
	| Leq -> CLeq
	| Greater-> CGreater
	| Geq -> CGeq
	| And -> CAnd
	| Or -> COr
	| Mod -> CMod

let trans_dir d = match d with
	  Redge -> CRedge
	| Ledge -> CLedge
	| Uedge -> CUedge

let num_from_leaf f = match f with
	  Leaf p -> p
	| _ -> -1


let gethead ( s : tree ) treename = match s with
	 SubTree (i,e,tl) -> CId (Cvar (treename ^ "[" ^ (string_of_int i) ^ "]"))
	| Leaf (i) -> CId (Cvar (treename ^ "[" ^ (string_of_int i) ^ "]"))

let rec cstmtlist_of_edge_declarations name size =
	if size = 0 
	then []
	else
		(CAssign(CArrayStat(name, size - 1), CCallNew("DiGrEdge",[]) ) )		 
		:: (cstmtlist_of_edge_declarations name (size-1))

let rec cstmtlist_of_tree_declarations name size = 
	if size = 0 
	then []
	else
	   ( CAssign(CArrayStat(name, size - 1), CCallNew("DiGrNode",[]) ) ) :: 
	(cstmtlist_of_tree_declarations name (size - 1))

let cstmtlist_from_tree tree treename =
	let rec treefold element stmt_list treename = match element with
	     Leaf (i) -> stmt_list
	   | SubTree (i,e,tl) ->
		(
		match e with
			Redge ->
           			List.fold_right (fun b a -> treefold b a treename)
                           			 tl
                            			(stmt_list @ (List.map 
							(fun f -> CExpr (CCallNew ("DiGrEdge",
[CId (Cvar (treename ^ "[" ^ (string_of_int i) ^ "]"));
(gethead f treename)])) )  tl))

			| Ledge ->
          			 List.fold_right (fun b a -> treefold b a treename)
                           			 tl
                            			(stmt_list @ (List.map 
(fun f -> CExpr (CCallNew 
		("DiGrEdge",[(gethead f treename); 
		CId (Cvar (treename ^ "[" ^ (string_of_int i) ^ "]"))])) )  tl))

			| Uedge ->
          			 List.fold_right (fun b a -> treefold b a treename)
                            	tl
                            	(stmt_list @ (List.map (fun f -> CExpr (CCallNew 
				("DiGrEdge",[CId (Cvar (treename ^ "[" ^ (string_of_int i) ^ "]")); 
				(gethead f treename); CLiteral_String("true")])) )  tl))
	)
	in
	treefold tree [] treename


let rec cvar_from_var v = match v with
	  VarId (s) -> Cvar(s)
	| ArrayIndStat (name, index) -> CArrayStat(name, index)
	| ArrayIndDyn (name, index) -> CArrayDyn(name, (cvar_from_var index))
	|  RecVar (v, s) -> (print_endline "ERROR: this should never be called?!"; 
	Cvar(s))

let rec cexpr_from_expr ( e : expr ) = match e with
	  Lit_Flt f -> CLiteral_Float(f)
	| Lit_Str s -> CLiteral_String(s)
	| Lit_Int i -> CLiteral_Int(i)
	| Actuals a -> 
			(print_endline ("ERROR: can't assign list to single object"); 
			CNoexpr)
	| Binop (e1, o, e2) -> CBinop (cexpr_from_expr e1, cop_from_op o, cexpr_from_expr e2)
	| NodeInEdge (v,e) -> 
			CObjCall(Cvar (varname_from_variable v), "getInEdge", [cexpr_from_expr e])
	| NodeOutEdge (v,e) ->
			CObjCall(Cvar (varname_from_variable v), "getOutEdge", [cexpr_from_expr e])
	| NodeChild (v,e) -> 
		CObjCall(Cvar (varname_from_variable v), "getChild", [cexpr_from_expr e])
	| NodeParent (v,e) -> 
		  CObjCall(Cvar (varname_from_variable v), "getParent", [cexpr_from_expr e])
		| NodeInEdges v -> CObjCall(Cvar (varname_from_variable v), "InEdges", [])
		| NodeOutEdges v -> CObjCall(Cvar (varname_from_variable v), "OutEdges", [])
		| EdgeInNode v -> CObjCall(Cvar (varname_from_variable v), "inNode", [])
		| EdgeOutNode v -> CObjCall(Cvar (varname_from_variable v), "outNode", [])
	| Variable v -> 
		(
		    match v with 
			  VarId s -> CId (Cvar s)
			| RecVar (v,s) -> 
				CObjCall(Cvar(varname_from_variable v), "getAttribute", [CId(Cvar("\"" ^ s ^ "\""))])
			| ArrayIndStat (s,i) -> CId(CArrayStat(s,i))
			| ArrayIndDyn (s,v) -> CId(CArrayDyn(s, Cvar(varname_from_variable v)))
		   )

let rec cexprlist_from_actualsexpr e = match e with
		Actuals a -> CActuals(List.map (fun m -> cexpr_from_expr m) a)
	| _ -> CActuals([])

let ctype_from_typ (t : typ) = match t with
	   Node -> CDiGrNode
	|  Int -> CInt
	|  Flt -> CFloat
	|  Str -> CString
	|  Edg -> CDiGrEdge

let rec cstmt_from_stmt (s : stmt ) = match s with
	   Print l -> CPrint(List.map cexpr_from_expr l)
	|  Call (s, l) -> CExpr(CCall (s, (List.map cexpr_from_expr l)))
	|  CallRule -> CExpr(CCall ("rule", [CId(Cvar("current")); 
	CId(Cvar("queue"))]))
	|  SetRule r -> CAssignRule("rule", CId(Cvar(r)))
	|  Assign (v, e) -> (
				match v with
					  VarId (s) -> 
						CAssign(Cvar(s), cexpr_from_expr e)
					| ArrayIndStat (name, index) -> 
						CAssign(CArrayStat(name,index), cexpr_from_expr e) 
					| ArrayIndDyn (name, index) -> 
						CAssign(CArrayDyn(name, (cvar_from_var index)), cexpr_from_expr e) 
		    			| RecVar (v, s) -> CExpr(CObjCall(
Cvar(varname_from_variable v), 
"setAttribute" ,
[CId(Cvar("\"" ^ s ^ "\"")); 
							cexpr_from_expr e]   ))
			)
	|  RAdd n -> CExpr(CObjCall(Cvar("returnQueue"),"push_back", [CId(Cvar((varname_from_variable n)))]))
	|  RAddFront n -> CExpr(CObjCall(	Cvar("returnQueue"),
						"push_front", 
						[CId(Cvar((varname_from_variable n)))]))

	|  RAddBy (s, t, rp, i) -> CExpr(
	       CCall ("DiGrAddBy", [CId(Cvar("current")); CId(Cvar("returnQueue")); 
		CId(Cvar("BACK")); 
		  (match t with AddByNode -> CId(Cvar("ADDBY_NODE")) | AddByEdge -> 
		CId(Cvar("ADDBY_EDGE")));
		  CLiteral_String("\"" ^ s ^ "\"");
		  ( match rp with Dolr -> CId(Cvar("DESCENDING")) | Tilde -> 
		CId(Cvar("ASCENDING")));
		  CLiteral_Int(i)]
		     ))
	|  RAddByFront (s, t, rp, i) -> CExpr(
	       CCall ( "DiGrAddBy", 
			[CId(Cvar("current")); CId(Cvar("returnQueue")); 
		CId(Cvar("FRONT")); 
		  (match t with AddByNode -> CId(Cvar("ADDBY_NODE")) | AddByEdge -> 
			CId(Cvar("ADDBY_EDGE")));
		  CLiteral_String("\"" ^ s ^ "\"");
		  ( match rp with Dolr -> CId(Cvar("DESCENDING")) 
				| Tilde -> CId(Cvar("ASCENDING")));
		 		 CLiteral_Int(i)]
		     ))
	|  Crawl (s, el, a1, a2) -> CExpr(CCall (s,
	   [CId(Cvar((varname_from_variable a1))); CId(Cvar(a2))] @ (List.map cexpr_from_expr el)
	   ) )
	| 	CreateGraph (variable, tree) -> 
	(
	match variable with
		   RecVar (v1, v2) -> 
				( print_endline "ERROR: only arrays can be assigned to a connection context";
				 CExpr(CNoexpr) )
		|  VarId s->
				 ( print_endline "ERROR: only arrays can be assigned to a connection context" ; 
				CExpr(CNoexpr) )
		| ArrayIndDyn (s,i) -> 
			( print_endline "ERROR: only statically-sized arrays can be assigned to a connection context" ;
			 CExpr(CNoexpr) )
		| ArrayIndStat (name, size) ->
		    CBlock(
		      CDeclare(CSigArr(CTypePointer(CDiGrNode),CArrayStat(name,size)))
		      :: (
			 (List. rev (cstmtlist_of_tree_declarations name size)) @
			 (cstmtlist_from_tree tree name)
			 )
		    )
		)
		|  Declare_Only (t, v) ->  
		(
		   match t with 
		   Node -> 
		   (
		      match v with 
			VarId (s) ->  CDeclareAssign (CSigPtr(CDiGrNode,s), CCallNew("DiGrNode",[]))
	      			| ArrayIndStat (name, size) -> CBlock(
			
					CDeclare(CSigArr(CTypePointer(CDiGrNode),CArrayStat(name,size)))
					:: (
					(List.rev (cstmtlist_of_tree_declarations name size))
					)
				)
	      | ArrayIndDyn (s, e) -> 
			( print_endline ("ERROR: cannot declare a type for an element of array" ^ s); 
			CExpr(CNoexpr) )
	      | RecVar (v, s) -> ( print_endline ("ERROR: cannot declare a type for an attribute" ^ 
				(varname_from_variable v)); 
				CExpr(CNoexpr) )
	   )

	| Edg -> 
	   (
	      match v with 

		VarId (s) ->  
			CDeclareAssign (CSigPtr(CDiGrEdge,s), CCallNew("DiGrEdge",[]))
	      | ArrayIndStat (name, size) -> CBlock(
				CDeclare(CSigArr(CTypePointer(CDiGrEdge),CArrayStat(name,size)))
				:: (
					(List.rev (cstmtlist_of_edge_declarations name size))
				)
				)
	      | ArrayIndDyn (s, e) -> 
			( print_endline ("ERROR: cannot declare a type for an element of array" ^ s);
			 CExpr(CNoexpr) )
	      | RecVar (v, s) -> ( print_endline ("ERROR: cannot declare a type for an attribute" ^ 
(varname_from_variable v));
				 CExpr(CNoexpr) )
	   )
	   | _ ->
	   (
	      match v with 

		VarId (s) ->  CDeclare (CSigVar(ctype_from_typ t,s)) 
	     	| ArrayIndStat (s,i) -> CDeclare(CSigArr(ctype_from_typ t, CArrayStat(s, i)))
		| ArrayIndDyn (s, i) -> 
			CDeclare(CSigArr(ctype_from_typ t, CArrayDyn(s, cvar_from_var i)))
			| _ ->  CExpr(CNoexpr)
	   )
	)
	| Declare(t, v, e) -> 
	   (
			match t with
			Node ->
			(
	      		match v with 
				VarId (s) ->  (
					match e with 
						Variable a -> CDeclareAssign 
							(CSigPtr(CDiGrNode,s), cexpr_from_expr e)
						| EdgeInNode a ->
	 						CDeclareAssign (CSigPtr(CDiGrNode,s), cexpr_from_expr e)
						| EdgeOutNode a -> 
							CDeclareAssign (CSigPtr(CDiGrNode,s), cexpr_from_expr e)
						| NodeChild(_,_) -> CDeclareAssign (CSigPtr(CDiGrNode,s), cexpr_from_expr e)
						| NodeParent(_,_) -> CDeclareAssign (CSigPtr(CDiGrNode,s), cexpr_from_expr e)
						| _ ->	 CExpr(CNoexpr)
					)
				| ArrayIndStat(name,size) -> 
					CDeclareAssign(CSigArr(CTypePointer(CDiGrNode),
								CArrayStat(name,size)), 
cexprlist_from_actualsexpr e)
	      		| _ ->  CExpr(CNoexpr)
			)
			| Edg ->
			(
	      		match v with 
				VarId (s) ->  (
					match e with
						  Variable a -> CDeclareAssign (CSigPtr(CDiGrEdge,s), cexpr_from_expr e)
						| NodeOutEdge(_,_) -> 
CDeclareAssign (CSigPtr(CDiGrEdge,s), cexpr_from_expr e)
						| NodeInEdge(_,_) -> 
							CDeclareAssign (CSigPtr(CDiGrEdge,s), cexpr_from_expr e)
						| _ -> CExpr(CNoexpr)
					)

				| ArrayIndStat(s,z) -> 
					CDeclareAssign (CSigArr(CDiGrEdge,
								CArrayStat(s,z)),
cexprlist_from_actualsexpr e)
	      		| _ ->  CExpr(CNoexpr)
			)
			| _ -> 
			(
				match v with
				 VarId (s) -> 
						CDeclareAssign(CSigVar(ctype_from_typ t, s), cexpr_from_expr e)
				| ArrayIndStat(s,z) -> 
					CDeclareAssign (CSigArr(ctype_from_typ t,
								CArrayStat(s,z)),
cexprlist_from_actualsexpr e)
				|	_ -> CExpr(CNoexpr)
			)
	   )
		| Expr e -> CExpr (cexpr_from_expr e)
		| EdgeCreation (s1, e, s2) -> 
		   ( 
		      match e with 
			Redge -> 
			CExpr(CCallNew ("DiGrEdge",[CId (Cvar (varname_from_variable s1)); 
				CId (Cvar (varname_from_variable s2))]))
		      | Ledge -> 
				CExpr(CCallNew ("DiGrEdge",[CId (Cvar (varname_from_variable s2)); 
				CId (Cvar (varname_from_variable s1))]))
		      | Uedge -> CExpr(CCallNew ("DiGrEdge",[CId (Cvar (varname_from_variable s1)); 
				CId (Cvar (varname_from_variable s2)); CLiteral_String("true")]))
		   )
		| If (e, sl1, sl2) ->
	   CIf( cexpr_from_expr e, List.map cstmt_from_stmt sl1, List.map 
	cstmt_from_stmt sl2)
	| While (e, sl) ->
	   CWhile( cexpr_from_expr e, List.map cstmt_from_stmt sl)

let auto_crawl_formals = [CSigPtr(CDiGrNode,"current"); 
	CFuncFormal(	CVoid,
			"rule", 
			[CTypePointer(CDiGrNode); CTypePointer(CVector(CTypePointer(CDiGrNode)))]
		)]
let auto_rule_formals = [CSigPtr(CDiGrNode,"current"); 
	CSigPtr(CVector(CTypePointer(CDiGrNode)),"returnQueue")]

let csigvar_from_formal f = match f with
	Validate (d, t, s) -> (match d with
	    In -> (match t with 
		  Int -> CSigVar(CInt,s) 
		| Flt -> CSigVar(CFloat,s) 
		| Str -> CSigVar(CString,s) 
		| Node -> CSigPtr(CDiGrNode, s)
		| Edg -> CSigPtr(CDiGrEdge, s)
		    )
	|    Out -> (match t with 
		  Int -> CSigRef(CInt,s) 
		| Flt -> CSigRef(CFloat,s) 
		| Str -> CSigRef(CString,s) 
		| Node -> CSigPtr(CDiGrNode, s)
		| Edg -> CSigPtr(CDiGrEdge, s)
		    )
	)

let add_to_list e l = e :: l
let merge_two_lists l1 l2 = List.fold_right add_to_list l1 l2

let cfdecl_from_fdecl (f : func_decl) =
	(if f.func_type = "opt" 
	then
	  (
		if f.fname = "main" 
		then
		     { cfname = f.fname; 
		       creturntype = CInt; 
		       cformals = (List.map csigvar_from_formal f.formals); 
		       cbody = [CTryCatchBlock(List.map cstmt_from_stmt f.body)] }
		else 
		     { cfname = f.fname; 
		       creturntype = CVoid; 
		       cformals = (List.map csigvar_from_formal f.formals); 
		       cbody = (List.map cstmt_from_stmt f.body) }
		  )
	else 	if f.func_type = "crawl" 
		then
		  { cfname = f.fname; 
		    creturntype = CVoid; 
		    cformals = auto_crawl_formals @ (List.map csigvar_from_formal f.formals);

	   	 cbody = 
			[
			       CDeclareAssign(CSigPtr(CVector(CTypePointer(CDiGrNode)),"queue"), 
			CCallNew("deque<DiGrNode*>",[])) ;
			       CExpr(CObjCall(Cvar("queue"),"push_back",[CId(Cvar("current"))])) ;
			       CDoWhile(CBinop(CId(Cvar("queue->size()")) , CGreater, CLiteral_Int(0)),
				       (
					 CAssign(Cvar("current"), CId(Cvar("queue->front()"))) :: 
				       (
					   CExpr(CId(Cvar("queue->pop_front()"))) ::
					   (List.map cstmt_from_stmt f.body)) ) 
			)]


		 }
		else
		  { cfname = f.fname; 
		    creturntype = CVoid; 
		    cformals = auto_rule_formals; 
		    cbody = 
		       (List.map cstmt_from_stmt f.body)
		   }
	)


let cast_from_ast (p : program) = match p with 
	(fdecllist) ->
	 	List.map cfdecl_from_fdecl (List.rev fdecllist)

